//! [`Menu`] and [`MenuItem`] widgets are used to create menu chains like standard `File`, `Edit`, etc. menus. See doc
//! of respective widget for more info and usage examples.

#![warn(missing_docs)]

use crate::{
    border::BorderBuilder,
    brush::Brush,
    core::{
        algebra::Vector2, color::Color, parking_lot::Mutex, pool::Handle, reflect::prelude::*,
        type_traits::prelude::*, uuid_provider, visitor::prelude::*,
    },
    decorator::{DecoratorBuilder, DecoratorMessage},
    define_constructor,
    grid::{Column, GridBuilder, Row},
    message::{ButtonState, MessageDirection, OsEvent, UiMessage},
    popup::{Placement, Popup, PopupBuilder, PopupMessage},
    stack_panel::StackPanelBuilder,
    text::TextBuilder,
    utils::{make_arrow_primitives, ArrowDirection},
    vector_image::VectorImageBuilder,
    widget::{Widget, WidgetBuilder, WidgetMessage},
    BuildContext, Control, HorizontalAlignment, Orientation, RestrictionEntry, Thickness, UiNode,
    UserInterface, VerticalAlignment, BRUSH_BRIGHT, BRUSH_BRIGHT_BLUE, BRUSH_PRIMARY,
};
use fyrox_core::variable::InheritableVariable;
use fyrox_graph::{BaseSceneGraph, SceneGraph};
use std::{
    ops::{Deref, DerefMut},
    sync::mpsc::Sender,
    sync::Arc,
};

/// A set of messages that can be used to manipulate a [`Menu`] widget at runtime.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MenuMessage {
    /// Activates the menu so it captures mouse input by itself and allows you to open menu item by a simple mouse
    /// hover.
    Activate,
    /// Deactivates the menu.
    Deactivate,
}

impl MenuMessage {
    define_constructor!(
        /// Creates [`MenuMessage::Activate`] message.
        MenuMessage:Activate => fn activate(), layout: false
    );
    define_constructor!(
        /// Creates [`MenuMessage::Deactivate`] message.
        MenuMessage:Deactivate => fn deactivate(), layout: false
    );
}

/// A set of messages that can be used to manipulate a [`MenuItem`] widget at runtime.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MenuItemMessage {
    /// Opens the menu item's popup with inner items.
    Open,
    /// Closes the menu item's popup with inner items.
    Close,
    /// The message is generated by a menu item when it is clicked.
    Click,
    /// Adds a new item to the menu item.
    AddItem(Handle<UiNode>),
    /// Removes an item from the menu item.
    RemoveItem(Handle<UiNode>),
    /// Sets the new items of the menu item.
    Items(Vec<Handle<UiNode>>),
}

impl MenuItemMessage {
    define_constructor!(
        /// Creates [`MenuItemMessage::Open`] message.
        MenuItemMessage:Open => fn open(), layout: false
    );
    define_constructor!(
          /// Creates [`MenuItemMessage::Close`] message.
        MenuItemMessage:Close => fn close(), layout: false
    );
    define_constructor!(
          /// Creates [`MenuItemMessage::Click`] message.
        MenuItemMessage:Click => fn click(), layout: false
    );
    define_constructor!(
          /// Creates [`MenuItemMessage::AddItem`] message.
        MenuItemMessage:AddItem => fn add_item(Handle<UiNode>), layout: false
    );
    define_constructor!(
          /// Creates [`MenuItemMessage::RemoveItem`] message.
        MenuItemMessage:RemoveItem => fn remove_item(Handle<UiNode>), layout: false
    );
    define_constructor!(
          /// Creates [`MenuItemMessage::Items`] message.
        MenuItemMessage:Items => fn items(Vec<Handle<UiNode>>), layout: false
    );
}

/// Menu widget is a root widget of an arbitrary menu hierarchy. An example could be "standard" menu strip with `File`, `Edit`, `View`, etc.
/// items. Menu widget can contain any number of children item (`File`, `Edit` in the previous example). These items should be [`MenuItem`]
/// widgets, however you can use any widget type (for example - to create some sort of a separator).
///
/// ## Examples
///
/// The next example creates a menu with the following structure:
///
/// ```text
/// |  File |  Edit |
/// |--Save |--Undo
/// |--Load |--Redo
/// ```
///
/// ```rust
/// # use fyrox_ui::{
/// #     core::pool::Handle,
/// #     menu::{MenuBuilder, MenuItemBuilder, MenuItemContent},
/// #     widget::WidgetBuilder,
/// #     BuildContext, UiNode,
/// # };
/// #
/// fn create_menu(ctx: &mut BuildContext) -> Handle<UiNode> {
///     MenuBuilder::new(WidgetBuilder::new())
///         .with_items(vec![
///             MenuItemBuilder::new(WidgetBuilder::new())
///                 .with_content(MenuItemContent::text_no_arrow("File"))
///                 .with_items(vec![
///                     MenuItemBuilder::new(WidgetBuilder::new())
///                         .with_content(MenuItemContent::text_no_arrow("Save"))
///                         .build(ctx),
///                     MenuItemBuilder::new(WidgetBuilder::new())
///                         .with_content(MenuItemContent::text_no_arrow("Load"))
///                         .build(ctx),
///                 ])
///                 .build(ctx),
///             MenuItemBuilder::new(WidgetBuilder::new())
///                 .with_content(MenuItemContent::text_no_arrow("Edit"))
///                 .with_items(vec![
///                     MenuItemBuilder::new(WidgetBuilder::new())
///                         .with_content(MenuItemContent::text_no_arrow("Undo"))
///                         .build(ctx),
///                     MenuItemBuilder::new(WidgetBuilder::new())
///                         .with_content(MenuItemContent::text_no_arrow("Redo"))
///                         .build(ctx),
///                 ])
///                 .build(ctx),
///         ])
///         .build(ctx)
/// }
/// ```
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
pub struct Menu {
    widget: Widget,
    active: bool,
}

crate::define_widget_deref!(Menu);

uuid_provider!(Menu = "582a04f3-a7fd-4e70-bbd1-eb95e2275b75");

impl Control for Menu {
    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        if let Some(msg) = message.data::<MenuMessage>() {
            match msg {
                MenuMessage::Activate => {
                    if !self.active {
                        ui.push_picking_restriction(RestrictionEntry {
                            handle: self.handle(),
                            stop: false,
                        });
                        self.active = true;
                    }
                }
                MenuMessage::Deactivate => {
                    if self.active {
                        self.active = false;
                        ui.remove_picking_restriction(self.handle());

                        // Close descendant menu items.
                        let mut stack = self.children().to_vec();
                        while let Some(handle) = stack.pop() {
                            let node = ui.node(handle);
                            if let Some(item) = node.cast::<MenuItem>() {
                                ui.send_message(MenuItemMessage::close(
                                    handle,
                                    MessageDirection::ToWidget,
                                ));
                                // We have to search in popup content too because menu shows its content
                                // in popup and content could be another menu item.
                                stack.push(*item.popup);
                            }
                            // Continue depth search.
                            stack.extend_from_slice(node.children());
                        }
                    }
                }
            }
        }
    }

    fn handle_os_event(
        &mut self,
        _self_handle: Handle<UiNode>,
        ui: &mut UserInterface,
        event: &OsEvent,
    ) {
        // Handle menu items close by clicking outside of menu item. We using
        // raw event here because we need to know the fact that mouse was clicked
        // and we do not care which element was clicked so we'll get here in any
        // case.
        if let OsEvent::MouseInput { state, .. } = event {
            if *state == ButtonState::Pressed && self.active {
                // TODO: Make picking more accurate - right now it works only with rects.
                let pos = ui.cursor_position();
                if !self.widget.screen_bounds().contains(pos) {
                    // Also check if we clicked inside some descendant menu item - in this
                    // case we don't need to close menu.
                    let mut any_picked = false;
                    let mut stack = self.children().to_vec();
                    'depth_search: while let Some(handle) = stack.pop() {
                        let node = ui.node(handle);
                        if let Some(item) = node.cast::<MenuItem>() {
                            let popup = ui.node(*item.popup);
                            if popup.screen_bounds().contains(pos) && popup.is_globally_visible() {
                                // Once we found that we clicked inside some descendant menu item
                                // we can immediately stop search - we don't want to close menu
                                // items popups in this case and can safely skip all stuff below.
                                any_picked = true;
                                break 'depth_search;
                            }
                            // We have to search in popup content too because menu shows its content
                            // in popup and content could be another menu item.
                            stack.push(*item.popup);
                        }
                        // Continue depth search.
                        stack.extend_from_slice(node.children());
                    }

                    if !any_picked {
                        ui.send_message(MenuMessage::deactivate(
                            self.handle(),
                            MessageDirection::ToWidget,
                        ));
                    }
                }
            }
        }
    }
}

/// A set of possible placements of a popup with items of a menu item.
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Hash, Visit, Reflect, Default, Debug)]
pub enum MenuItemPlacement {
    /// Bottom placement.
    Bottom,
    /// Right placement.
    #[default]
    Right,
}

/// Menu item is a widget with arbitrary content, that has a "floating" panel (popup) for sub-items if the menu item. This was menu items can form
/// arbitrary hierarchies. See [`Menu`] docs for examples.
#[derive(Default, Clone, Debug, Visit, Reflect, ComponentProvider)]
pub struct MenuItem {
    /// Base widget of the menu item.
    pub widget: Widget,
    /// Current items of the menu item
    pub items: InheritableVariable<Vec<Handle<UiNode>>>,
    /// A handle of a popup that holds the items of the menu item.
    pub popup: InheritableVariable<Handle<UiNode>>,
    /// A handle of a panel widget that arranges items of the menu item.
    pub panel: InheritableVariable<Handle<UiNode>>,
    /// Current placement of the menu item.
    pub placement: InheritableVariable<MenuItemPlacement>,
    /// A flag, that defines whether the menu item is clickable when it has sub-items or not.
    pub clickable_when_not_empty: InheritableVariable<bool>,
    /// A handle to the decorator of the item.
    pub decorator: InheritableVariable<Handle<UiNode>>,
}

crate::define_widget_deref!(MenuItem);

// MenuItem uses popup to show its content, popup can be top-most only if it is
// direct child of root canvas of UI. This fact adds some complications to search
// of parent menu - we can't just traverse the tree because popup is not a child
// of menu item, instead we trying to fetch handle to parent menu item from popup's
// user data and continue up-search until we find menu.
fn find_menu(from: Handle<UiNode>, ui: &UserInterface) -> Handle<UiNode> {
    let mut handle = from;
    while handle.is_some() {
        if let Some((_, popup)) = ui.find_component_up::<Popup>(handle) {
            // Continue search from parent menu item of popup.
            handle = popup
                .user_data_cloned::<Handle<UiNode>>()
                .unwrap_or_default();
        } else {
            // Maybe we have Menu as parent for MenuItem.
            return ui.find_handle_up(handle, &mut |n| n.cast::<Menu>().is_some());
        }
    }
    Default::default()
}

fn is_any_menu_item_contains_point(ui: &UserInterface, pt: Vector2<f32>) -> bool {
    for (handle, menu) in ui
        .nodes()
        .pair_iter()
        .filter_map(|(h, n)| n.query_component::<MenuItem>().map(|menu| (h, menu)))
    {
        if ui.find_component_up::<Menu>(handle).is_none()
            && menu.is_globally_visible()
            && menu.screen_bounds().contains(pt)
        {
            return true;
        }
    }
    false
}

fn close_menu_chain(from: Handle<UiNode>, ui: &UserInterface) {
    let mut handle = from;
    while handle.is_some() {
        let popup_handle = ui.find_handle_up(handle, &mut |n| n.has_component::<Popup>());

        if let Some(popup) = ui.node(popup_handle).query_component::<Popup>() {
            ui.send_message(PopupMessage::close(
                popup_handle,
                MessageDirection::ToWidget,
            ));

            // Continue search from parent menu item of popup.
            handle = popup
                .user_data_cloned::<Handle<UiNode>>()
                .unwrap_or_default();
        } else {
            // Prevent infinite loops.
            break;
        }
    }
}

uuid_provider!(MenuItem = "72e002c6-6060-4583-b5b7-0c5500244fef");

impl Control for MenuItem {
    fn on_remove(&self, sender: &Sender<UiMessage>) {
        // Popup won't be deleted with the menu item, because it is not the child of the item.
        // So we have to remove it manually.
        sender
            .send(WidgetMessage::remove(
                *self.popup,
                MessageDirection::ToWidget,
            ))
            .unwrap();
    }

    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
        self.widget.handle_routed_message(ui, message);

        if let Some(msg) = message.data::<WidgetMessage>() {
            match msg {
                WidgetMessage::MouseDown { .. } => {
                    let menu = find_menu(self.parent(), ui);
                    if menu.is_some() {
                        // Activate menu so it user will be able to open submenus by
                        // mouse hover.
                        ui.send_message(MenuMessage::activate(menu, MessageDirection::ToWidget));

                        ui.send_message(MenuItemMessage::open(
                            self.handle(),
                            MessageDirection::ToWidget,
                        ));
                    }
                }
                WidgetMessage::MouseUp { .. } => {
                    if !message.handled() {
                        if self.items.is_empty() || *self.clickable_when_not_empty {
                            ui.send_message(MenuItemMessage::click(
                                self.handle(),
                                MessageDirection::ToWidget,
                            ));
                        }
                        if self.items.is_empty() {
                            let menu = find_menu(self.parent(), ui);
                            if menu.is_some() {
                                // Deactivate menu if we have one.
                                ui.send_message(MenuMessage::deactivate(
                                    menu,
                                    MessageDirection::ToWidget,
                                ));
                            } else {
                                // Or close menu chain if menu item is in "orphaned" state.
                                close_menu_chain(self.parent(), ui);
                            }
                        }
                        message.set_handled(true);
                    }
                }
                WidgetMessage::MouseEnter => {
                    // While parent menu active it is possible to open submenus
                    // by simple mouse hover.
                    let menu = find_menu(self.parent(), ui);
                    let open = if menu.is_some() {
                        if let Some(menu) = ui.node(menu).cast::<Menu>() {
                            menu.active
                        } else {
                            false
                        }
                    } else {
                        true
                    };
                    if open {
                        ui.send_message(MenuItemMessage::open(
                            self.handle(),
                            MessageDirection::ToWidget,
                        ));
                    }
                }
                _ => {}
            }
        } else if let Some(msg) = message.data::<MenuItemMessage>() {
            if message.destination() == self.handle
                && message.direction() == MessageDirection::ToWidget
            {
                match msg {
                    MenuItemMessage::Open => {
                        if !self.items.is_empty() {
                            let placement = match *self.placement {
                                MenuItemPlacement::Bottom => Placement::LeftBottom(self.handle),
                                MenuItemPlacement::Right => Placement::RightTop(self.handle),
                            };

                            // Open popup.
                            ui.send_message(PopupMessage::placement(
                                *self.popup,
                                MessageDirection::ToWidget,
                                placement,
                            ));
                            ui.send_message(PopupMessage::open(
                                *self.popup,
                                MessageDirection::ToWidget,
                            ));
                            ui.send_message(DecoratorMessage::select(
                                *self.decorator,
                                MessageDirection::ToWidget,
                                true,
                            ));
                        }
                    }
                    MenuItemMessage::Close => {
                        ui.send_message(PopupMessage::close(
                            *self.popup,
                            MessageDirection::ToWidget,
                        ));
                        ui.send_message(DecoratorMessage::select(
                            *self.decorator,
                            MessageDirection::ToWidget,
                            false,
                        ));
                    }
                    MenuItemMessage::Click => {}
                    MenuItemMessage::AddItem(item) => {
                        ui.send_message(WidgetMessage::link(
                            *item,
                            MessageDirection::ToWidget,
                            *self.panel,
                        ));
                        self.items.push(*item);
                    }
                    MenuItemMessage::RemoveItem(item) => {
                        if let Some(position) = self.items.iter().position(|i| *i == *item) {
                            self.items.remove(position);

                            ui.send_message(WidgetMessage::remove(
                                *item,
                                MessageDirection::ToWidget,
                            ));
                        }
                    }
                    MenuItemMessage::Items(items) => {
                        for &current_item in self.items.iter() {
                            ui.send_message(WidgetMessage::remove(
                                current_item,
                                MessageDirection::ToWidget,
                            ));
                        }

                        for &item in items {
                            ui.send_message(WidgetMessage::link(
                                item,
                                MessageDirection::ToWidget,
                                *self.panel,
                            ));
                        }

                        self.items.set_value_and_mark_modified(items.clone());
                    }
                }
            }
        }
    }

    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
        // We need to check if some new menu item opened and then close other not in
        // direct chain of menu items until to menu.
        if message.destination() != self.handle() {
            if let Some(MenuItemMessage::Open) = message.data::<MenuItemMessage>() {
                let mut found = false;
                let mut handle = message.destination();
                while handle.is_some() {
                    if handle == self.handle() {
                        found = true;
                        break;
                    } else {
                        let node = ui.node(handle);
                        if let Some(popup) = node.cast::<Popup>() {
                            // Once we found popup in chain, we must extract handle
                            // of parent menu item to continue search.
                            handle = popup
                                .user_data_cloned::<Handle<UiNode>>()
                                .unwrap_or_default();
                        } else {
                            handle = node.parent();
                        }
                    }
                }

                if !found {
                    ui.send_message(MenuItemMessage::close(
                        self.handle(),
                        MessageDirection::ToWidget,
                    ));
                }
            }
        }
    }

    fn handle_os_event(
        &mut self,
        _self_handle: Handle<UiNode>,
        ui: &mut UserInterface,
        event: &OsEvent,
    ) {
        // Allow closing "orphaned" menus by clicking outside of them.
        if let OsEvent::MouseInput { state, .. } = event {
            if *state == ButtonState::Pressed {
                if let Some(popup) = ui.node(*self.popup).query_component::<Popup>() {
                    if *popup.is_open {
                        // Ensure that cursor is outside of any menus.
                        if !is_any_menu_item_contains_point(ui, ui.cursor_position())
                            && find_menu(self.parent(), ui).is_none()
                        {
                            ui.send_message(PopupMessage::close(
                                *self.popup,
                                MessageDirection::ToWidget,
                            ));

                            // Close all other popups.
                            close_menu_chain(self.parent(), ui);
                        }
                    }
                }
            }
        }
    }
}

/// Menu builder creates [`Menu`] widgets and adds them to the user interface.
pub struct MenuBuilder {
    widget_builder: WidgetBuilder,
    items: Vec<Handle<UiNode>>,
}

impl MenuBuilder {
    /// Creates new builder instance.
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            items: Default::default(),
        }
    }

    /// Sets the desired items of the menu.
    pub fn with_items(mut self, items: Vec<Handle<UiNode>>) -> Self {
        self.items = items;
        self
    }

    /// Finishes menu building and adds them to the user interface.
    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
        for &item in self.items.iter() {
            if let Some(item) = ctx[item].cast_mut::<MenuItem>() {
                item.placement
                    .set_value_and_mark_modified(MenuItemPlacement::Bottom);
            }
        }

        let back = BorderBuilder::new(
            WidgetBuilder::new()
                .with_background(BRUSH_PRIMARY)
                .with_child(
                    StackPanelBuilder::new(
                        WidgetBuilder::new().with_children(self.items.iter().cloned()),
                    )
                    .with_orientation(Orientation::Horizontal)
                    .build(ctx),
                ),
        )
        .build(ctx);

        let menu = Menu {
            widget: self
                .widget_builder
                .with_handle_os_events(true)
                .with_child(back)
                .build(),
            active: false,
        };

        ctx.add_node(UiNode::new(menu))
    }
}

/// Allows you to set a content of a menu item either from a pre-built "layout" with icon/text/shortcut/arrow or a custom
/// widget.
pub enum MenuItemContent<'a, 'b> {
    /// Quick-n-dirty way of building elements. It can cover most of use cases - it builds classic menu item:
    ///
    /// ```text
    ///   _________________________
    ///  |    |      |        |   |
    ///  |icon| text |shortcut| > |
    ///  |____|______|________|___|
    /// ```
    Text {
        /// Text of the menu item.
        text: &'a str,
        /// Shortcut of the menu item.
        shortcut: &'b str,
        /// Icon of the menu item. Usually it is a [`crate::image::Image`] or [`crate::vector_image::VectorImage`] widget instance.
        icon: Handle<UiNode>,
        /// Create an arrow or not.
        arrow: bool,
    },
    /// Horizontally and Vertically centered text
    ///
    /// ```text
    ///   _________________________
    ///  |                        |
    ///  |          text          |
    ///  |________________________|
    /// ```
    TextCentered(&'a str),
    /// Allows to put any node into menu item. It allows to customize menu item how needed - i.e. put image in it, or other user
    /// control.
    Node(Handle<UiNode>),
}

impl<'a, 'b> MenuItemContent<'a, 'b> {
    /// Creates a menu item content with a text, a shortcut and an arrow (with no icon).
    pub fn text_with_shortcut(text: &'a str, shortcut: &'b str) -> Self {
        MenuItemContent::Text {
            text,
            shortcut,
            icon: Default::default(),
            arrow: true,
        }
    }

    /// Creates a menu item content with a text and an arrow (with no icon or shortcut).
    pub fn text(text: &'a str) -> Self {
        MenuItemContent::Text {
            text,
            shortcut: "",
            icon: Default::default(),
            arrow: true,
        }
    }

    /// Creates a menu item content with a text only (with no icon, shortcut, arrow).
    pub fn text_no_arrow(text: &'a str) -> Self {
        MenuItemContent::Text {
            text,
            shortcut: "",
            icon: Default::default(),
            arrow: false,
        }
    }

    /// Creates a menu item content with only horizontally and vertically centered text.
    pub fn text_centered(text: &'a str) -> Self {
        MenuItemContent::TextCentered(text)
    }
}

/// Menu builder creates [`MenuItem`] widgets and adds them to the user interface.
pub struct MenuItemBuilder<'a, 'b> {
    widget_builder: WidgetBuilder,
    items: Vec<Handle<UiNode>>,
    content: Option<MenuItemContent<'a, 'b>>,
    back: Option<Handle<UiNode>>,
    clickable_when_not_empty: bool,
}

impl<'a, 'b> MenuItemBuilder<'a, 'b> {
    /// Creates new menu item builder instance.
    pub fn new(widget_builder: WidgetBuilder) -> Self {
        Self {
            widget_builder,
            items: Default::default(),
            content: None,
            back: None,
            clickable_when_not_empty: false,
        }
    }

    /// Sets the desired content of the menu item. In most cases [`MenuItemContent::text_no_arrow`] is enough here.
    pub fn with_content(mut self, content: MenuItemContent<'a, 'b>) -> Self {
        self.content = Some(content);
        self
    }

    /// Sets the desired items of the menu.
    pub fn with_items(mut self, items: Vec<Handle<UiNode>>) -> Self {
        self.items = items;
        self
    }

    /// Allows you to specify the background content. Background node is only for decoration purpose, it can be any kind of node,
    /// by default it is Decorator.
    pub fn with_back(mut self, handle: Handle<UiNode>) -> Self {
        self.back = Some(handle);
        self
    }

    /// Sets whether the menu item is clickable when it has sub-items or not.
    pub fn with_clickable_when_not_empty(mut self, value: bool) -> Self {
        self.clickable_when_not_empty = value;
        self
    }

    /// Finishes menu item building and adds it to the user interface.
    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
        let content = match self.content {
            None => Handle::NONE,
            Some(MenuItemContent::Text {
                text,
                shortcut,
                icon,
                arrow,
            }) => GridBuilder::new(
                WidgetBuilder::new()
                    .with_child(icon)
                    .with_child(
                        TextBuilder::new(
                            WidgetBuilder::new()
                                .with_margin(Thickness::left(2.0))
                                .on_row(1)
                                .on_column(1),
                        )
                        .with_text(text)
                        .build(ctx),
                    )
                    .with_child(
                        TextBuilder::new(
                            WidgetBuilder::new()
                                .with_horizontal_alignment(HorizontalAlignment::Right)
                                .with_margin(Thickness::uniform(1.0))
                                .on_row(1)
                                .on_column(2),
                        )
                        .with_text(shortcut)
                        .build(ctx),
                    )
                    .with_child(if arrow {
                        VectorImageBuilder::new(
                            WidgetBuilder::new()
                                .with_visibility(!self.items.is_empty())
                                .on_row(1)
                                .on_column(3)
                                .with_foreground(BRUSH_BRIGHT)
                                .with_horizontal_alignment(HorizontalAlignment::Center)
                                .with_vertical_alignment(VerticalAlignment::Center),
                        )
                        .with_primitives(make_arrow_primitives(ArrowDirection::Right, 8.0))
                        .build(ctx)
                    } else {
                        Handle::NONE
                    }),
            )
            .add_row(Row::stretch())
            .add_row(Row::auto())
            .add_row(Row::stretch())
            .add_column(Column::auto())
            .add_column(Column::stretch())
            .add_column(Column::auto())
            .add_column(Column::strict(10.0))
            .add_column(Column::strict(5.0))
            .build(ctx),
            Some(MenuItemContent::TextCentered(text)) => {
                TextBuilder::new(WidgetBuilder::new().with_margin(Thickness::left_right(5.0)))
                    .with_text(text)
                    .with_horizontal_text_alignment(HorizontalAlignment::Center)
                    .with_vertical_text_alignment(VerticalAlignment::Center)
                    .build(ctx)
            }
            Some(MenuItemContent::Node(node)) => node,
        };

        let decorator = self.back.unwrap_or_else(|| {
            DecoratorBuilder::new(
                BorderBuilder::new(WidgetBuilder::new())
                    .with_stroke_thickness(Thickness::uniform(0.0)),
            )
            .with_hover_brush(BRUSH_BRIGHT_BLUE)
            .with_selected_brush(BRUSH_BRIGHT_BLUE)
            .with_normal_brush(BRUSH_PRIMARY)
            .with_pressed_brush(Brush::Solid(Color::TRANSPARENT))
            .with_pressable(false)
            .build(ctx)
        });

        if content.is_some() {
            ctx.link(content, decorator);
        }

        let panel;
        let popup = PopupBuilder::new(WidgetBuilder::new().with_min_size(Vector2::new(10.0, 10.0)))
            .with_content({
                panel = StackPanelBuilder::new(
                    WidgetBuilder::new().with_children(self.items.iter().cloned()),
                )
                .build(ctx);
                panel
            })
            // We'll manually control if popup is either open or closed.
            .stays_open(true)
            .build(ctx);

        let menu = MenuItem {
            widget: self
                .widget_builder
                .with_handle_os_events(true)
                .with_preview_messages(true)
                .with_child(decorator)
                .build(),
            popup: popup.into(),
            items: self.items.into(),
            placement: MenuItemPlacement::Right.into(),
            panel: panel.into(),
            clickable_when_not_empty: false.into(),
            decorator: decorator.into(),
        };

        let handle = ctx.add_node(UiNode::new(menu));

        // "Link" popup with its parent menu item.
        if let Some(popup) = ctx[popup].cast_mut::<Popup>() {
            popup.user_data = Some(Arc::new(Mutex::new(handle)));
        }

        handle
    }
}
